/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkMethod, FunctionType, Local, Scene, Stmt } from 'arkanalyzer/lib';
import { BaseChecker, BaseMetaData } from '../BaseChecker';
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { Rule, Defects, MethodMatcher, MatcherTypes, MatcherCallback, CheckerUtils } from '../../Index';
import { IssueReport } from '../../model/Defects';

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'CallbackReleaseProperlyCheck');
const gMetaData: BaseMetaData = {
    severity: 1,
    ruleDocPath: 'docs/callback-release-properly-check.md',
    description: 'The callback used by the registered listener must be released reasonably.'
};
const offSigantrue = `@ohosSdk/api/@ohos.multimedia.avsession.d.ts: avSession.AVCastController.off('playbackStateChange', @ohosSdk/api/@ohos.multimedia.avsession.d.ts: avSession.AVCastController.%AM1(@ohosSdk/api/@ohos.multimedia.avsession.d.ts: avSession.AVPlaybackState))`;

export class CallbackReleaseProperlyCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];

    private methodMatcher: MethodMatcher = {
        matcherType: MatcherTypes.METHOD
    };

    public registerMatchers(): MatcherCallback[] {
        const matchMethodCb: MatcherCallback = {
            matcher: this.methodMatcher,
            callback: this.check
        };
        return [matchMethodCb];
    }

    public check = (arkMethod: ArkMethod): void => {
        let stmts = arkMethod.getCfg()?.getStmts();
        if (!stmts) {
            return;
        }
        this.processStmt(stmts, arkMethod.getDeclaringArkFile().getScene());
    };

    private processStmt(stmts: Stmt[], scene: Scene): void {
        for (const stmt of stmts) {
            const invoker = CheckerUtils.getInvokeExprFromStmt(stmt);
            if (!invoker) {
                continue;
            }
            if (invoker.getMethodSignature().toString() !== offSigantrue) {
                continue;
            }
            let arg = invoker.getArg(1);
            if (!(arg instanceof Local)) {
                continue;
            }
            let type = arg.getType();
            let decStmt = arg.getDeclaringStmt();
            if (type instanceof FunctionType) {
                let method = scene.getMethod(type.getMethodSignature());
                if (method && method.isAnonymousMethod() && !decStmt && arg.getName().includes('%')) {
                    this.reportIssue(stmt, 'off');
                }
            }
        }
    }

    private reportIssue(stmt: Stmt, methodName: string): void {
        const severity = this.rule.alert ?? this.metaData.severity;
        const filePath = stmt.getCfg().getDeclaringMethod().getDeclaringArkFile().getFilePath();
        const originPositionInfo = stmt.getOriginPositionInfo();
        const lineNum = originPositionInfo.getLineNo();
        const text = stmt.getOriginalText();
        if (!text || text.length === 0) {
            return;
        }
        const startColumn = originPositionInfo.getColNo() + text.lastIndexOf(methodName);
        const endColunm = startColumn + methodName.length - 1;
        let defects = new Defects(lineNum, startColumn, endColunm, this.metaData.description, severity, this.rule.ruleId,
            filePath, this.metaData.ruleDocPath, true, false, false);
        this.issues.push(new IssueReport(defects, undefined));
    }
}